/*
 *  Copyright (C) 2010 Martin Haulrich <mwh.isv@cbs.dk> and Matthias Buch-Kromann <mbk.isv@cbs.dk>
 *
 *  This file is part of the IncrementalParser package.
 *
 *  The IncrementalParser program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.osdtsystem.incparser.parsers;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import org.osdtsystem.incparser.data.CONLLSentence;
import org.osdtsystem.incparser.data.ConllIterator;
import org.osdtsystem.incparser.featureextraction.FeatureExtractor;
import org.osdtsystem.incparser.features.FeatureHandler;
import org.osdtsystem.incparser.features.WeightVector;
import org.osdtsystem.incparser.graphs.BaseGraph;
import org.osdtsystem.incparser.graphs.BaseGraphGrowable;
import org.osdtsystem.incparser.graphs.ConllAdapter;
import org.osdtsystem.incparser.graphs.Graph;
import org.osdtsystem.incparser.graphs.GraphScorerWithExtractors;
import org.osdtsystem.incparser.logging.Log;
import org.osdtsystem.incparser.loss.Loss;

/**
 * 
 * @author Martin Haulrich and Matthias Buch-Kromann
 */
public abstract class AbstractConllParser implements Parser {
    FeatureHandler stringHandler, edgeTypeHandler, featureHandler;
    int sentenceCounter = 0;
    int wordCounter = 0;
    double totalLoss = 0;
    boolean dump = false;

    public AbstractConllParser(FeatureHandler edgeTypeHandler,
            FeatureHandler stringHandler, FeatureHandler featureHandler) {
        this.edgeTypeHandler = edgeTypeHandler;
        this.stringHandler = stringHandler;
        this.featureHandler = featureHandler;
    }

    public int strings() {
        return stringHandler.features();
    }

    public int extractionFeatures() {
        return featureHandler.features();
    }

    public int edgeTypes() {
        return edgeTypeHandler.features();
    }

    public void printFeatureHandlers() {
        edgeTypeHandler.writeToFile("dump.etypes");
        featureHandler.writeToFile("dump.features");
        stringHandler.writeToFile("dump.strings");
    }

    abstract FeatureExtractor[][] featureExtractorGroups(ConllAdapter graph);

    public ConllAdapter newGraph(WeightVector weights) {
        // Create new graph
        BaseGraph baseGraph = new BaseGraphGrowable();
        ConllAdapter conllGraph = new ConllAdapter(baseGraph,
                edgeTypeHandler, stringHandler, featureHandler);

        // Create graph scorer and add it to the base graph
        for (FeatureExtractor[] featureExtractorGroup : featureExtractorGroups(conllGraph)) {
            new GraphScorerWithExtractors(baseGraph,
                weights, featureHandler, featureExtractorGroup);
        }

        // Return the new graph
        return conllGraph;
    }

    public void growFeatures(ConllAdapter gold) {
        gold.score();
    }

    public void stopGrowth() {
        edgeTypeHandler.stopGrowth();
        stringHandler.stopGrowth();
        featureHandler.stopGrowth();
    }

    public void parse(WeightVector weights, String inputFile, String outputFile)
            throws FileNotFoundException, IOException {
        parse(weights, inputFile, outputFile, null);
    }

    public void parse(WeightVector weights, String inputFile, String outputFile, Loss loss)
            throws FileNotFoundException, IOException {
        try {
            // Open input and output files
            ConllIterator conllIterator = new ConllIterator(inputFile);
            PrintWriter outputWriter = new PrintWriter(outputFile, "UTF-8");

            // Parse input file
            parse(weights, conllIterator, outputWriter);
            outputWriter.close();
            conllIterator.close();
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException("Java is supposed to always support UTF-8, but this Java version somehow didn't!", ex);
        }
    }

    public void parse(WeightVector weights, ConllIterator inputIterator, PrintWriter outputWriter) {
        parse(weights, inputIterator, outputWriter, null);
    }

    public void parse(WeightVector weights, ConllIterator inputIterator, PrintWriter outputWriter, Loss loss) {
        // Create new graph and counters for logging information
        ConllAdapter adapter = newGraph(weights);
        BaseGraph system = adapter.baseGraph();
        sentenceCounter = 0;
        wordCounter = 0;
        totalLoss = 0;
        
        // Create gold graph if requested
        boolean compareWithGold = loss != null;
        compareWithGold = true;
        ConllAdapter goldAdapter = compareWithGold ? newGraph(weights) : null;
        BaseGraph gold = compareWithGold ? goldAdapter.baseGraph() : null;
        
        // Iterate over all sentences in the input
        //dump = true;
        while (inputIterator.hasNext()) {
            // Read next conll sentence into graph
            CONLLSentence inputSentence = inputIterator.next();
            adapter.clear();
            inputSentence.writeToGraph(adapter);
            adapter.setConllSentence(inputSentence);
            
            //inputSentence.writeToGraph(adapter);
            //dump(system, "gold");
            adapter.clearEdges();
            adapter.clearGold();

            // Print logging information
            if (sentenceCounter % 100 == 0)
                Log.infoW(".");
            Log.phandler.openSentence();
            Log.phandler.setSentenceName(inputSentence.toTokenString());

            // Parse the sentence
            parse(weights, adapter);
            //dump(system, "system");

            // Write parse to output file
            adapter.writeConllGraph(outputWriter, system);
            
            // Update counters and loss
            sentenceCounter++;
            wordCounter += inputSentence.size() - 1;
            if (loss != null)
                totalLoss += loss.loss(goldAdapter, adapter);

            // Log end of sentence
            Log.phandler.closeSentence();
        }
    }

    void dump(Graph graph, String graphName) {
        if (dump) {
            stringHandler.writeToFile("dump.strings");
            edgeTypeHandler.writeToFile("dump.types");
            featureHandler.writeToFile("dump.features");
            graph.writeToFile("dump." + graphName);
        }
    }

    public int wordCounter() {
        return wordCounter;
    }

    public int sentenceCounter() {
        return sentenceCounter;
    }

    public double totalLoss() {
        return totalLoss;
    }
}
